package cn.deschen.eshop.auth.config;

import cn.deschen.eshop.auth.filter.DynamicSecurityFilter;
import cn.deschen.eshop.auth.hanlders.RestAuthenticationEntryPoint;
import cn.deschen.eshop.auth.hanlders.RestfulAccessDeniedHandler;
import cn.deschen.eshop.auth.properties.DefaultSecurityConstants;
import cn.deschen.eshop.auth.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @Author hanbin_chen
 * @Description security默认配置类，可继承自定义配置
 * @DateTime 2021/1/8 22:47
 * @Version V1.0.0
 */
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnMissingClass("cn.deschen.eshop.auth.config.DefaultSecurityConfig")
public class DefaultSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ValidateCodeAuthSecurityConfig validateCodeAuthSecurityConfig;

    @Autowired
    private JwtAuthSecurityConfig jwtAuthSecurityConfig;

    @Autowired
    private DynamicSecurityConfig dynamicSecurityConfig;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    /**
     * 对http请求进行权限配置
     * @param http  http请求
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry =
                http.authorizeRequests();
        // 对不需要鉴权的路径放行
        for (String ignoreUrl : securityProperties.getBrowser().getIgnoreUrls()) {
            registry.antMatchers(ignoreUrl).permitAll();
        }
        //允许跨域请求的OPTIONS请求
        registry.antMatchers(HttpMethod.OPTIONS)
                .permitAll();
        // 任何请求需要身份认证
        registry.and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                // 关闭跨站请求防护及不使用session
                .and()
                .csrf()
                .disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 自定义权限拦截器JWT过滤器
                .and()
                .apply(validateCodeAuthSecurityConfig)
                .and()
                .apply(jwtAuthSecurityConfig)
                .and()
                .apply(dynamicSecurityConfig)
                .and()
                .exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint)
        ;

    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    /**
     * 加密方式，必加因为现如今Spring Security中密码的存储格式是“{id}…………”.前面的id是加密方式,id可以是bcrypt、sha256等,
     * 后面跟着的是加密后的密码.也就是说,程序拿到传过来的密码的时候,会首先查找被“{”和“}”包括起来的id,来确定后面的密码是被怎么样加密的,
     * 如果找不到就认为id是null报异常
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
